/*
 * Copyright 2013 Sigurd Randoll.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package de.digiway.rapidbreeze.server.model.download.autopilot;

import de.digiway.rapidbreeze.server.config.ServerConfiguration;
import de.digiway.rapidbreeze.server.model.config.ConfigurationChangeListener;
import de.digiway.rapidbreeze.server.model.config.ConfigurationChangeListener.ConfigurationChangeEvent;
import de.digiway.rapidbreeze.server.model.download.Download;
import de.digiway.rapidbreeze.server.model.download.DownloadManager;
import de.digiway.rapidbreeze.server.model.download.DownloadManagerListener;
import de.digiway.rapidbreeze.server.model.download.action.DownloadManagerAction;
import de.digiway.rapidbreeze.server.model.download.DownloadEvent;
import de.digiway.rapidbreeze.shared.rest.config.DownloadSpeed;
import static de.digiway.rapidbreeze.shared.rest.config.DownloadSpeed.STOP;
import de.digiway.rapidbreeze.shared.rest.download.DownloadStatus;
import java.util.ArrayList;

/**
 * This "autopilot" controls a {@linkplain DownloadManager} instance. It starts,
 * pauses and restarts {@linkplain Download} instances automatically according
 * to the given {@linkplain ServerConfiguration}. It reacts on
 * {@linkplain ServerConfiguration} changes, e.g. speed changes will be
 * communicated to the underlaying {@linkplain DownloadManager} accordingly. The
 * autopilot must be enabled to start working, see {@linkplain #enable()}.
 *
 * @author Sigurd
 */
public class DownloadManagerAutopilot {

    private DownloadManager downloadManager;
    private ServerConfiguration serverConfiguration;
    private DownloadManagerListenerHandler downloadManagerListenerHandler;
    private ConfigurationChangeListenerHandler configurationChangeListenerHandler;

    /**
     * Creates a new instance to control the given {@linkplain DownloadManager}.
     *
     * @param downloadManager
     */
    public DownloadManagerAutopilot(DownloadManager downloadManager, ServerConfiguration serverConfiguration) {
        this.downloadManager = downloadManager;
        this.serverConfiguration = serverConfiguration;
        this.downloadManagerListenerHandler = new DownloadManagerListenerHandler();
        this.configurationChangeListenerHandler = new ConfigurationChangeListenerHandler();
    }

    /**
     * Enables the autopilot which will immeditatly starts controlling the
     * underlaying {@linkplain DownloadManager}.
     */
    public void enable() {
        this.downloadManager.addListener(downloadManagerListenerHandler);
        this.serverConfiguration.addListener(configurationChangeListenerHandler);
        startWaitingDownloads();
        throttleDownloads();
    }

    /**
     * Disables the autopilot. This will only disable the autopilot and not the
     * {@linkplain DownloadManager} instance which will keep running.
     */
    public void disable() {
        this.serverConfiguration.removeListener(configurationChangeListenerHandler);
        this.downloadManager.removeListener(downloadManagerListenerHandler);
    }

    private void startWaitingDownloads() {
        downloadManager.executeAtomic(new StartWaitingDownloads());
    }

    private void resumeDownloads() {
        downloadManager.executeAtomic(new ResumeDownloads());
    }

    private void stopOverflowDownloads() {
        downloadManager.executeAtomic(new StopOverflowDownloads());
    }

    private void throttleDownloads() {
        downloadManager.executeAtomic(new Throttle());
    }

    private void pauseAllRunning() {
        downloadManager.executeAtomic(new PauseAllRunning());
    }

    private abstract class AbstractAutopilotAction<T> implements DownloadManagerAction<T> {

        protected int getActiveDownloadCount() {
            int count = 0;
            for (Download download : downloadManager.getDownloads()) {
                if (DownloadStatus.RUNNING.equals(download.getDownloadStatus())) {
                    count++;
                }
            }
            return count;
        }

        protected int getParallelDownloads() {
            return serverConfiguration.getParallelDownloads(serverConfiguration.getDownloadSpeed());
        }
    }

    private class StopOverflowDownloads<Void> extends AbstractAutopilotAction {

        @Override
        public Void call() throws Exception {
            if (!serverConfiguration.getDownloadSpeed().equals(DownloadSpeed.STOP)) {
                int toStop = getActiveDownloadCount() - getParallelDownloads();
                if (toStop > 0) {
                    for (Download download : downloadManager.getDownloads()) {
                        if (toStop > 0 && download.getDownloadStatus().equals(DownloadStatus.RUNNING)) {
                            downloadManager.waitDownload(download);
                            toStop--;
                        }
                    }
                }
            }
            return null;
        }
    }

    private class StartWaitingDownloads<Void> extends AbstractAutopilotAction {

        @Override
        public Void call() {
            if (!serverConfiguration.getDownloadSpeed().equals(DownloadSpeed.STOP)) {
                int active = getActiveDownloadCount();
                for (Download download : new ArrayList<>(downloadManager.getDownloads())) {
                    if (active >= getParallelDownloads()) {
                        break;
                    }

                    if (DownloadStatus.WAITING.equals(download.getDownloadStatus())) {
                        downloadManager.startDownload(download);
                        active = getActiveDownloadCount();
                    }
                }
            }
            return null;
        }
    }

    private class ResumeDownloads<Void> extends AbstractAutopilotAction {

        @Override
        public Void call() {
            for (Download download : downloadManager.getDownloads()) {
                if (download.getDownloadStatus().equals(DownloadStatus.PAUSE)) {
                    if (getActiveDownloadCount() < serverConfiguration.getParallelDownloads(serverConfiguration.getDownloadSpeed())) {
                        downloadManager.startDownload(download);
                    } else {
                        downloadManager.waitDownload(download);
                    }
                }
            }
            return null;
        }
    }

    private class Throttle<Void> extends AbstractAutopilotAction {

        @Override
        public Void call() {
            int speed = serverConfiguration.getKbPerSecond(serverConfiguration.getDownloadSpeed()) * 1024;
            downloadManager.setThrottle(speed);
            return null;
        }
    }

    private class PauseAllRunning<Void> extends AbstractAutopilotAction {

        @Override
        public Void call() {
            for (Download download : downloadManager.getDownloads()) {
                if (download.getDownloadStatus().equals(DownloadStatus.RUNNING)) {
                    downloadManager.pauseDownload(download);
                }
            }
            return null;
        }
    }

    private class ProcessConfigurationChange<Void> extends AbstractAutopilotAction {

        private ConfigurationChangeEvent event;

        private ProcessConfigurationChange(ConfigurationChangeEvent event) {
            this.event = event;
        }

        @Override
        public Void call() {
            if (event.getProperty().equals(ServerConfiguration.PROP_DOWNLOAD_SPEED)) {
                switch ((DownloadSpeed) event.getNewValue()) {
                    case STOP:
                        pauseAllRunning();
                        return null;
                    default:
                        resumeDownloads();
                        startWaitingDownloads();
                        stopOverflowDownloads();
                        throttleDownloads();
                }
            }
            return null;
        }
    }

    private class ConfigurationChangeListenerHandler implements ConfigurationChangeListener {

        @Override
        public void onConfigurationChange(ConfigurationChangeEvent event) {
            downloadManager.executeAtomic(new ProcessConfigurationChange(event));
        }
    }

    private class DownloadManagerListenerHandler implements DownloadManagerListener {

        @Override
        public void onDownloadAdded(Download download) {
            startWaitingDownloads();
        }

        @Override
        public void onDownloadRemoved(Download download) {
            startWaitingDownloads();
        }

        @Override
        public void onDownloadStatusChange(DownloadEvent event) {
            startWaitingDownloads();
        }
    }
}
